package cn.itcast.order.service.impl;

import cn.itcast.account.api.AccountClient;
import cn.itcast.order.entity.Order;
import cn.itcast.order.mapper.OrderMapper;
import cn.itcast.order.model.req.CreateOrderOfHighConcurrencyReq;
import cn.itcast.order.service.OrderService;
import cn.itcast.storage.api.StorageClient;
import feign.FeignException;
import io.seata.spring.annotation.GlobalTransactional;
import lombok.extern.slf4j.Slf4j;
import org.orz.cloud.common.exception.BusinessException;
import org.orz.cloud.lock.LockUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.DataAccessException;
import org.springframework.data.redis.core.RedisOperations;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.SessionCallback;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

/**
 * @author 虎哥
 */
@Slf4j
@Service
public class OrderServiceImpl implements OrderService {
    @Autowired
    private AccountClient accountClient;
    @Autowired
    private StorageClient storageClient;
    @Autowired
    private OrderMapper orderMapper;
    @Autowired
    private TransactionalTestClient transactionalTestClient;
    @Autowired
    private RedisTemplate<String,Object> redisTemplate;
    /**商品分布式锁锁前缀*/
    private static final String COMMODITY_LOCK_PREFIX = "COMMODITY_LOCK_PREFIX_";


    @Override
    @GlobalTransactional
    public Long create(Order order) {
        // 创建订单
        orderMapper.insert(order);
        try {
            // 扣库存
            storageClient.deduct(order.getCommodityCode(), order.getCount());
            log.info("扣库存成功");
            // 扣款
            accountClient.debit(order.getUserId(), order.getMoney());
            log.info("扣款成功");
        } catch (FeignException e) {
            log.error("下单失败，原因:{}", e.contentUTF8(),e);
            throw new RuntimeException(e.contentUTF8());
        }
        return order.getId();
    }

    @Override
    public void createOrderOfHighConcurrency(CreateOrderOfHighConcurrencyReq req) {
        String commodityKey = COMMODITY_LOCK_PREFIX + req.getCommodityNo();
        //----------1.对商品id加锁--------------------------------------------------------------------------------------
        // 是否有必要添加分布式锁这个有待考虑。如果说只是为了锁住商品不让其他请求对商品及库存操作，那应该没必要
        // 因为redis事务自带的有乐观锁的性质，在事务执行的时候如果库存被其他请求修改过，会返回错误信息。
        // 但是是否需要对当前用户进行加锁呢？或者当前用户的账户进行加锁，这个问题值得思考，我认为在可以多点登录的时候，
        // 是需要的。
//        if(!LockUtil.lock(commodityKey,2000)){
//            throw new BusinessException("999","业务繁忙，请稍后重试");
//        }
//        try {
            //----------2.检查并且扣除在redis中的库存--------------------------------------------------------------------
            redisTemplate.execute(new SessionCallback<Object>() {
                @Override
                public Object execute(RedisOperations operations) throws DataAccessException {
                    checkAndDeductCommodity(operations,commodityKey);
                    return null;
                }
            });
            //----------3.库存扣减完成后，发送消息队列做后面的操作--------------------------------------------------------
            //比如生成订单、支付等操作，以及对库存最终的增减做持久化到MySQL中的操作
//            mq.sendMessage()......
//        } finally {
//            //业务执行完成，释放锁
//            LockUtil.release();
//        }
    }

    /**
     * 检查并且扣减库存
     */
    @SuppressWarnings({"unchecked"})
    private void checkAndDeductCommodity(RedisOperations operations, String commodityKey) {
        //监控当前商品
        operations.watch(commodityKey);
        //做商品库存剩余量的校验
        Object commodityStockObj = redisTemplate.opsForValue().get(commodityKey);
        if(commodityStockObj == null || (Integer)commodityStockObj < 1){
            operations.unwatch();
            throw new BusinessException("999","商品库存不足");
        }
        Integer commodityStock = (Integer)commodityStockObj;
        //开启事务
        operations.multi();
        //扣减库存
        operations.opsForValue().set(commodityKey, commodityStock - 1);
        //记录购买人的信息……
//        operations.opsForSet().add()
        try {
            //提交事务
            operations.exec();
        } catch (Exception e){
            //如果key值在提交时已经被改变，这里就会抛出异常
            //这里可以做业务上的异常处理
            throw new BusinessException("999","当前抢购人数过多，请稍后重试");
        }
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void testThreadTransaction() {
        System.out.println("当前："+Thread.currentThread().getName());
        //测试线程与事务
        Order order = new Order();
        order.setUserId("主线程");
        order.setCommodityCode("主线程商品");
        order.setCount(5);
        order.setMoney(3);
        orderMapper.insert(order);
        new Thread(()->{
            System.out.println("进入线程："+Thread.currentThread().getName());
            Order updateOrder = new Order();
            updateOrder.setUserId("子线程");
            updateOrder.setCount(1);
            orderMapper.insert(updateOrder);
        }).start();
        throw new RuntimeException("手动抛出异常");
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void testNotSupported() {
        Order order = new Order();
        order.setUserId("mainUser");
        order.setCommodityCode("主方法的商品");
        order.setCount(1);
        order.setMoney(1);
        orderMapper.insert(order);
        transactionalTestClient.testNotSupported();
    }
}
